Jquery Specific

Patterns




Created By Akshit Malhotra

OutLINE

  1. Understanding Parents and Children 

  2. Know Your Selector

  3. Refactoring PokEPON

  4. Performance Case Study

Jquery Myths

Oh wait, its a GOD Object!

Who cares how JQuery works as long as its doing its what I want.....

Lets debunk all these myths....

Its a big blackbox !

Understand Parents And Children

Understand Parents And Children

                            //Context
$('.child', $parent).show();
                            //Selector String
$('#parent .child').show();
                            //Immediate Children
$parent.children('.child').show();
                            //Find
$parent.find('.child').show();
                            //Created Context i.e $expr, context
$('.child', $('#parent')).show();
                            //Child Combinator selector
$('#parent > .child')).show();

1. $(‘#whats .the’, context)

                            //Context
$('.child', $parent).show();
  • This is first parsed, which is slow and then translated to: $parent.find('.child').show()
  • This is not bad but almost 5~10% slower than the fastest option

2.  Selector String

                            //Selector String
$('#parent .child').show();
  • This is the slowest amongst all.
  • ​Bad as it will match ".child" before checking its a direct  child of "parent" .

3. Immediate  Child

  • Internally uses $.sibling and JS native nextSibling() to find nodes following other nodes in the  tree.
  • ​~70% slower than the fastest option
                            //Immediate Children
$parent.children('.child').show();

4. Created  Context

  • Equivalent internally to  $(#parent).find('.child')
  • ~16% slower than the fastest option, plus its not readable
                            //Created Context
$('.child', $('#parent')).show();
                            //Context
$('.child', $parent).show();

Different from what we had before!!!

5. Child  selector

Same as Selector String: $('parent  .child')

                            //Child Combinator selector
$('#parent > .child')).show();

6. Best way...

                            //Context and find() 
$parent.find('.child').show();
Fastest 
                            //Selector and find()
$('#parent').find('.child').show();
But  watch out, this is slow again

JSPerf Results

Summary

jQuery Object

Parsing

Tokenization

Context Evaluation

OutLINE

  1. Understanding Parents and Children 

  2. Know Your Selector

  3. Refactoring PokePon

  4. Performance Case Study

Parse Direction

                            div.superheroes table.attendees .batman

Sizzle'ing From Right to Left

Left And Right

                            //Bad
div.superheroes .batman

//Better
.superheroes cool.batman

Specific on the right, light on the left

Dont overdo IT

                            // This Doesn't help 
div.superheroes table.attendees td.batman

// Rather, just do this
.superheroes cool.batman

Be Specific only when needed

Descend from #ID

                            // This is slow

$('.superheroes .batman')

ID selectors are backed by native JS method (getElementByID)

                            // Descending from ID is better

$('#superheroesID .batman')

Don't do this

But, Code this

JSPerf ID v/s Class

Cache your SelectorS

                            // antipattern
$('.alwaysClickMe').click(function () {
    $('.batman').hide();
});

Caching is your friend

                            // preferred
var $batman = $('.batman');

// prefix the cache with $ 
// to help identify it as a selector cache later
$('.alwaysClickMe').click(function () {
    $batman.hide();
});

But  there is a  problem with this too?

Cache your SelectorS

                            // ahhhhh nooooo
var $batman = $('.batman');
var $superwoman = $('.superwoman');
var $catwoman = $('.catwoman');
var $spiderman = $('.spiderman');
.
.
and
.
.
on
.
.
on

Caching is not your friend anymore :(

Cache your SelectorS

                            function Selector_Cache() {
    var collection = {};

    function get_from_cache( selector ) {
        if ( undefined === collection[ selector ] ) {
            collection[ selector ] = $( selector );
        }

        return collection[ selector ];
    }

    return { get: get_from_cache };
}

var selectors = new Selector_Cache();

// Usage $( '#element' ) becomes
selectors.get( '#element' );

Caching is again your friend :)

http://ttmm.io/tech/selector-caching-jquery/

Chaining 'em UP

                            var $parents = $('#parents');
$parents.doSomething()
        .doSomethingElse()
        .doSomethingElseElse();

Avoid requery by using jQuery chaining

Less Code and easier to write

                            $('#parents').doSomething();
$('#parents').doSomethingElse();
$('#parents').doSomethingElseElse();

Noooo!

Yes Yes Yes!

JSPerf cache & Chain

Chaining and Caching is the way to go

http://jsperf.com/jquery-chaining

SELECT YOUR SELECTORS CAREFULLY

How the hell can I keep track of all these selectors mistakes?

  • jQuery source code &  documentation
  • JSPerf results to see if your selectors could be improved
  • And most importantly, use Chrome developer tools

OutLINE

  1. Understanding Parents and Children 

  2. Know Your Selector

  3. Refactoring PokePon

  4. Performance Case Study

$(" Refactor #Pokepon')

  PokePon Game

(Firebase API award at the PennApps Hackathon 2013)

https://github.com/thanhhaimai/pokepon

M ultiplayer game combining battle elements of the Pokemon game with the dance and rhythm elements

$(" Refactor #Pokepon')

  PokePon Highlights  

Lobby.js  listens to new players being added.

Client.js  starting the game and handling client parameters such as name and id of player.

Game.js  responsible for all the game logic plus event handling

 

$(" Refactor #Pokepon')

//FileName: "Lobby.js" which listens to new players being added

$(function() {
  gamesRef = new Firebase(baseUrl + "games");
  loadGamesList();
});

var loadGamesList = function() {
  gamesRef.on('child_added', function(data) {
    var game = data.val();
    if (!game.id) {
      return;
    }
    $("#gamesList").append('<li id="' + game.id 
                        + '"><a href="/games/' + game.id + '">' 
                        + game.id + '</a></li>');
  });
}

What is the problem with this??

$(" Refactor #Pokepon')

var $gamesList = $("#gamesList");

var loadGamesList = function() {
  gamesRef.on('child_added', function(data) {
    var game = data.val();
    if (!game.id) {
      return;
    }

    $gamesList.append('<li id="' + game.id 
                        + '"><a href="/games/' + game.id + '">' 
                        + game.id + '</a></li>');

  });
}

Cache it !!!!

$(" Refactor #Pokepon')

//FileName: "client.js", responsible for starting the game 
//and, handling client parameters such as name and id of player

self.socket.on('gameStart', function(data) {
    .
    .
    .
    .
    if (data.player1 === self.id) {
      console.log("I'm on the left", data.pokepon2);
      $('#you .name').html("melaniec");
      $('#opponent .name').html("sdjidjev");

      self.pokeponRef = new Firebase(url1);
      self.enemyRef = new Firebase(url2);

      .
      .
      .
    }

What about this one?

$(" Refactor #Pokepon')

self.socket.on('gameStart', function(data) {
    .
    .
    if (data.player1 === self.id) {
      console.log("I'm on the left", data.pokepon2);
      
      $('#you').find('.name').html("melaniec");
      $('#opponent').find('.name').html("sdjidjev");

      self.pokeponRef = new Firebase(url1);
      self.enemyRef = new Firebase(url2);

      .
      .
    }

use Find(), remember?

$(" Refactor #Pokepon')

//FileName: "game.js", responsible for all the game logic plus event handling
function selectMusic() {
    .
    .
    for (var j = 0; j < tracks.length; ++j) {
            (function(index) {
                  var track = tracks[index];
                  var url = track.artwork_url;
                  if (!url) {
                      url = track.waveform_url;
                  }
                  var $track = $('<div class="track">'+
                              '<img src="'+url+'"></img>'+
                              '<div>'+track.title+'</div>');
                  $track.click(function() {
                        $soundcloudSelector.fadeOut();
                        var trackId = (/(\d+)/.exec(track.stream_url))[1];
                        client.loadMusic(trackId);
                  });

                  $soundcloudSelector.append($track);

            })(j); //self executing function call

     }//end for loop
.
.
}

What is wrong here?

$(" Refactor #Pokepon')

function selectMusic() {
    var tracksHtml = "";
    for (var j = 0; j < tracks.length; ++j) {
            (function(index) {
                  var track = tracks[index];
                  var url = track.artwork_url;
                  if (!url) {
                      url = track.waveform_url;
                  }
                  var $track = $('<div class="track">'+
                              '<img src="'+url+'"></img>'+
                              '<div>'+track.title+'</div>');
                  $track.click(function() {
                        $soundcloudSelector.fadeOut();
                        var trackId = (/(\d+)/.exec(track.stream_url))[1];
                        client.loadMusic(trackId);
                  });
                  
                  tracksHtml += $track;
                  //$soundcloudSelector.append($track);

            })(j); //self executing function call

     }//end for loop
     
     $soundcloudSelector.append(tracksHtml);
.
.
}

Appending outside loop

OutLINE

  1. Understanding Parents and Children 

  2. Know Your Selector

  3. Refactoring PokePon

  4. Performance Case Study

Wikipedia Webapp Startup

  Testing Desktop experience of the wikipedia visual  editor

https://en.wikipedia.org/wiki/Barack_Obama?veaction=edit

Wikipedia Webapp Startup

Wiki Dev team working......

Wikipedia Webapp Startup

Chrome Profile Timeline with Causes & JS Profiler on.

 

Wikipedia Webapp Startup

Profiler suggests too many .hide() calls

Wikipedia Webapp Startup

What did they find out?

  • More $(elem).hide()  by which  jQuery asks for computedstyle when you tell it to hide an element. It’s a big problem with performance
  • Issue raised:  https://github.com/jquery/jquery.com/issues/88#issuecomment-72400007  
  • Wikipedia feedback: "yeah, there are a lot of $.show / $.hide calls that are wasteful because they're toggling the visibility of something we know is hidden or visible"

Final Words

Important to know how selectors can optimize your code.
Caching is an area which can give you awesome performance.
Parent and Child relationship should be handled with care.
And lastly, don't treat jQuery as a giant blackbox, explore inside.

References

Thank You